#include "RenderObject.hpp"


RenderObject::RenderObject(const string fn , unsigned int flags, GLenum ptype){
  location[0] = 0.0;
  location[1] = 0.0;
  location[2] = 0.0;
  size = 1.0;


  diffOver = false;
  specOver = false;
  ambiOver = false;
  diffTexOver = false;

  
  primitiveType = ptype;
  switch(ptype){
  case GL_TRIANGLES:
    primitiveSize = 3;
    break;
  case GL_PATCHES:
    primitiveSize = 3;
    break;
  case GL_QUADS:
    primitiveSize = 4;
    break;
  default:
    primitiveSize = 3;
    break;
  }
  

  filename = fn;
  scene = importer.ReadFile(fn,flags);
  if (!scene || scene->mNumMeshes <= 0 ) {
    std::cerr <<"Importer erro for "<<fn<<"\t"<< importer.GetErrorString() << std::endl;
    exit(-1);
  }

  //  printSceneStats();
  initTextures();
  initVertices();

}

RenderObject::~RenderObject(){
  for(unsigned int mi = 0; mi <scene->mNumMeshes; mi++){
    delete [] indexBuffer[mi];
  }
  delete[] indexBuffer;
  delete[] indexSize;
}


void RenderObject::Render(Shader* s){
  shader = s;
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();{
    glTranslatef(location[0], location[1], location[2]);
    glScalef(size, size,size);
    drawNode(scene->mRootNode);
  }
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
}

void RenderObject::drawNode(aiNode* n){
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  {
    glMultTransposeMatrixf((GLfloat*)(n->mTransformation[0]));
    
    //descend into children
    for(unsigned int c = 0; c < n->mNumChildren; c++){
      drawNode(n->mChildren[c]);
    }
    
    //draw this node's meshes
    for(unsigned int m = 0; m< n->mNumMeshes; m++){
      setMaterial(scene->mMeshes[n->mMeshes[m]]->mMaterialIndex);
      drawMesh(n->mMeshes[m]);
    }
  } 
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
}


void RenderObject::setMaterial(int m){
  aiMaterial* material = scene->mMaterials[m];
  aiColor3D color;

  //texture maps
  if(diffuseMap[m]!=NULL ||diffTexOver){
    GLint diffuse = shader->GetLocation( "diffuseMap");
    if(diffuse!=-1){
      glUniform1i(diffuse, 0); // The diffuse map will be GL_TEXTURE0
      glActiveTexture(GL_TEXTURE0);
      BindDiffuseTex(m);
      diffuse = shader->GetLocation(  "hasdiff");
      glUniform1i(diffuse, 1);
    }
  } else {
    GLint diffuse = shader->GetLocation( "hasdiff");
    if(diffuse!=-1)
      glUniform1i(diffuse, 0);
  }

  if(specularMap[m]!=NULL){
    GLint specular = shader->GetLocation( "specularMap");
    if(specular!=-1){
      glUniform1i(specular, 1); // The specular  map will be GL_TEXTURE1
      glActiveTexture(GL_TEXTURE1);
      specularMap[m]->Bind();
      specular = shader->GetLocation( "hasspec");
      glUniform1i(specular, 1);
    }
  } else  {
    GLint specular = shader->GetLocation(  "hasspec");
    if(specular!=-1)
      glUniform1i(specular, 0);
  }
    
  if(normalMap[m]!=NULL){
    GLint normal = shader->GetLocation(  "normalMap");
    if(normal!=-1){
      glActiveTexture(GL_TEXTURE2);
      glUniform1i(normal, 2); // The normal  map will be GL_TEXTURE2
      normalMap[m]->Bind();
      normal = shader->GetLocation( "hasnorm");
      glUniform1i(normal, 1);
    }
  } else {
    GLint normal = shader->GetLocation( "hasnorm");
    if(normal!=-1)
      glUniform1i(normal, 0);
  }


  //material
  GLint diffuse = shader->GetLocation(  "Kd");
  Diffuse(material,color);
  if(diffuse!=-1)
    glUniform4f(diffuse, color.r, color.g, color.b, 1.0);

  // Specular material
  GLint specular = shader->GetLocation(  "Ks");
  Specular(material, color);
  if(specular!=-1)
    glUniform4f(specular, color.r, color.g, color.b, 1.0);
  
  // Ambient material
    
  GLint ambient = shader->GetLocation(  "Ka");
  Ambient(material, color);
  if(ambient!=-1)
    glUniform4f(ambient, color.r, color.g, color.b, 1.0);

  // Specular power
  GLint shininess = shader->GetLocation( "alpha");
  float value;
  if (AI_SUCCESS == material->Get(AI_MATKEY_SHININESS, value)) {
    value = 128;
  } else {
    value = 128;
  }
  if(shininess!=-1)
    glUniform1f(shininess, value);
}

void RenderObject::drawMesh(int m){
  GLint location;
  location = shader->GetAttributeLocation("positionIn");
  if(scene->mMeshes[m]->HasPositions()){
    glBindBufferARB(GL_ARRAY_BUFFER_ARB,vobIDs[m*NumVertexAttrib+VertexPosition]);
    glEnableVertexAttribArray(location);
    glVertexAttribPointer(location, 3, GL_FLOAT, 0, sizeof(aiVector3D), 0);
  } else {
    glDisableVertexAttribArray(location);
  }

  location = shader->GetAttributeLocation("normalIn");
  if(location!= -1 && scene->mMeshes[m]->HasNormals()){
    glBindBufferARB(GL_ARRAY_BUFFER_ARB,vobIDs[m*NumVertexAttrib+VertexNormal]);
    glEnableVertexAttribArray(location);
    glVertexAttribPointer(location, 3, GL_FLOAT, 0, sizeof(aiVector3D), 0);
  }else {
    glDisableVertexAttribArray(location);
  }
  
  location = shader->GetAttributeLocation("texcoordIn");
  if(scene->mMeshes[m]->HasTextureCoords(0)){
    glBindBufferARB(GL_ARRAY_BUFFER_ARB,vobIDs[m*NumVertexAttrib+VertexTexture]);
    glEnableVertexAttribArray(location);
    glVertexAttribPointer(location, 2, GL_FLOAT, 0, sizeof(aiVector3D), 0);    
  } else {
    glDisableVertexAttribArray(location);
  }

  location = shader->GetAttributeLocation("tangentIn");
  if(location!=-1 && scene->mMeshes[m]->HasTangentsAndBitangents()){
    glBindBufferARB(GL_ARRAY_BUFFER_ARB,vobIDs[m*NumVertexAttrib+VertexTangent]);
    glEnableVertexAttribArray(location);
    glVertexAttribPointer(location, 3, GL_FLOAT, 0, sizeof(aiVector3D), 0);    
  } else {
    glDisableVertexAttribArray(location);
  }

  location = shader->GetAttributeLocation("bitangentIn");
  if(location!=-1  && scene->mMeshes[m]->HasTangentsAndBitangents()){
    glBindBufferARB(GL_ARRAY_BUFFER_ARB,vobIDs[m*NumVertexAttrib+VertexBitangent]);
    glEnableVertexAttribArray(location);
    glVertexAttribPointer(location, 3, GL_FLOAT, 0, sizeof(aiVector3D), 0);   
  }
  else {
    glDisableVertexAttribArray(location);
  }






  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, vobIDs[m*NumVertexAttrib+VertexElementIndex]);
  glDrawElements(primitiveType,  indexSize[m], GL_UNSIGNED_INT, 0);
  

  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
  glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);
}



//build the vertices drawing indices 
void RenderObject::initVertices(){
  //build element array
  indexSize = new int[scene->mNumMeshes];
  indexBuffer = new unsigned int* [scene->mNumMeshes];
  vobIDs.resize(scene->mNumMeshes*NumVertexAttrib);
  for(unsigned int mi = 0; mi <scene->mNumMeshes; mi++){
    indexSize [mi] = scene->mMeshes[mi]->mNumFaces * primitiveSize;
    indexBuffer[mi] = new unsigned int[scene->mMeshes[mi]->mNumFaces*primitiveSize];
    for (unsigned i = 0; i < scene->mMeshes[mi]->mNumFaces; i++) {
      for (unsigned j = 0; j < scene->mMeshes[mi]->mFaces[i].mNumIndices; j++) {
	indexBuffer[mi][i*primitiveSize+j] = scene->mMeshes[mi]->mFaces[i].mIndices[j];
      }
    }

    //build vertex buffer object
    GLuint tempvob;
    
    //index
    {
      tempvob = 0;
      glGenBuffersARB(1, &tempvob);
      vobIDs[mi*NumVertexAttrib+VertexElementIndex]=tempvob;
      glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, tempvob);
      glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB,
		      sizeof(unsigned int)*indexSize[mi],
		      indexBuffer[mi],
		      GL_STATIC_DRAW_ARB);
    }


    if(scene->mMeshes[mi]->HasPositions()){
      tempvob = 0;

      glGenBuffersARB(1, &tempvob);
      vobIDs[mi*NumVertexAttrib+VertexPosition]=tempvob;
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, tempvob); 
      glBufferDataARB(GL_ARRAY_BUFFER_ARB,
		      sizeof(aiVector3D)*scene->mMeshes[mi]->mNumVertices, 
		      scene->mMeshes[mi]->mVertices, 
		      GL_STATIC_DRAW_ARB);
    }
    if(scene->mMeshes[mi]->HasNormals()){
      tempvob = 0;
      glGenBuffersARB(1, &tempvob);
      vobIDs[mi*NumVertexAttrib+VertexNormal]=tempvob;
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, tempvob); 
      glBufferDataARB(GL_ARRAY_BUFFER_ARB,
		      sizeof(aiVector3D)*scene->mMeshes[mi]->mNumVertices, 
		      scene->mMeshes[mi]->mNormals, 
		      GL_STATIC_DRAW_ARB);
    }
    if(scene->mMeshes[mi]->HasTextureCoords(0)){
      tempvob = 0;
      glGenBuffersARB(1, &tempvob);
      vobIDs[mi*NumVertexAttrib+VertexTexture]=tempvob;
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, tempvob); 
      glBufferDataARB(GL_ARRAY_BUFFER_ARB,
		      sizeof(aiVector3D)*scene->mMeshes[mi]->mNumVertices, 
		      scene->mMeshes[mi]->mTextureCoords[0], 
		      GL_STATIC_DRAW_ARB);
    }
    if(scene->mMeshes[mi]->HasTangentsAndBitangents()){
      tempvob = 0;
      glGenBuffersARB(1, &tempvob);
      vobIDs[mi*NumVertexAttrib+VertexTangent]=tempvob;
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, tempvob); 
      glBufferDataARB(GL_ARRAY_BUFFER_ARB,
		      sizeof(aiVector3D)*scene->mMeshes[mi]->mNumVertices, 
		      scene->mMeshes[mi]->mTangents, 
		      GL_STATIC_DRAW_ARB);
    }
    if(scene->mMeshes[mi]->HasTangentsAndBitangents()){
      tempvob = 0;
      glGenBuffersARB(1, &tempvob);
      vobIDs[mi*NumVertexAttrib+VertexBitangent]=tempvob;
      glBindBufferARB(GL_ARRAY_BUFFER_ARB, tempvob); 
      glBufferDataARB(GL_ARRAY_BUFFER_ARB,
		      sizeof(aiVector3D)*scene->mMeshes[mi]->mNumVertices, 
		      scene->mMeshes[mi]->mBitangents, 
		      GL_STATIC_DRAW_ARB);
    }
  }


  glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);
  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
}



//build the texture map array
void RenderObject::initTextures(){
  for(unsigned int i = 0; i<scene->mNumMaterials; i++){
    string dfile("textures/");
    string sfile("textures/");
    string nfile("textures/");

    bool loadOK = false;
    diffuseMap[i] = NULL;
    specularMap[i] = NULL;
    normalMap[i] = NULL;
    
    //get the texture names 
    aiString texname;
    scene->mMaterials[i]->GetTexture(aiTextureType_DIFFUSE, 0,&texname);
    //try to load all 3 texture maps, fails oh well
    if(texname!=aiString("")){
      dfile+=texname.data;
      diffuseMap[i] = new sf::Image();
      loadOK = diffuseMap[i]->LoadFromFile(dfile);
      if(!loadOK){
	delete(diffuseMap[i]);
	diffuseMap[i] = NULL;
      } else {
	glActiveTexture(GL_TEXTURE0);
	diffuseMap[i]->Bind();

	glGenerateMipmapEXT(GL_TEXTURE_2D);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
      }
    }
  }
}


void RenderObject::printSceneStats(){
  
  //get the texture names 
  aiString texname;
  cout<<"scene "<<filename<<endl;
  cout<<"number of meshs "<< scene->mNumMeshes<<endl;
  cout<<"number of materials "<< scene->mNumMaterials<<endl;  

  for(int i = 0; i<scene->mNumMaterials; i++){
    aiString name;
    scene->mMaterials[i]->Get(AI_MATKEY_NAME,name);
    cout<<name.data<<"\t Number of properties"<<scene->mMaterials[i]->mNumProperties<<endl;
    cout<<"Diffuse texture count "<<scene->mMaterials[i]->GetTextureCount(aiTextureType_DIFFUSE)<<endl;
    if(scene->mMaterials[i]->GetTextureCount(aiTextureType_DIFFUSE)!=0){
      scene->mMaterials[i]->GetTexture(aiTextureType_DIFFUSE, 0,&texname);
      cout<<texname.data<<endl;
    }
    cout<<"Specu texture count "<<scene->mMaterials[i]->GetTextureCount(aiTextureType_SPECULAR)<<endl;
    cout<<"Normal texture count "<<scene->mMaterials[i]->GetTextureCount(aiTextureType_NORMALS)<<endl;
    for(int j = 0; j< scene->mMaterials[i]->mNumProperties; j++){     
      cout<<"\t"<<scene->mMaterials[i]->mProperties[j]->mKey.data<<endl;
    }
  }
  cout<<endl;
}


void RenderObject::Diffuse(aiMaterial* mat, aiColor3D &color){
  if(diffOver){
    color = diff;
  } else {
    if (AI_SUCCESS ==mat->Get(AI_MATKEY_COLOR_DIFFUSE, color)){
      
    } else {
      color.r = color.g = color.b =0.0;
    }
    
  }
}
void RenderObject::Specular(aiMaterial* mat, aiColor3D &color){
  if(specOver){
    color = spec;
  } else {
    if (AI_SUCCESS ==mat->Get(AI_MATKEY_COLOR_SPECULAR, color)){
      
    } else {
      color.r = color.g = color.b =0.0;
    }
    
  }
}
void RenderObject::Ambient(aiMaterial* mat, aiColor3D &color){
  if(ambiOver){
    color = ambi;
  } else {
    if (AI_SUCCESS ==mat->Get(AI_MATKEY_COLOR_AMBIENT, color)){
    } else {
      color.r = color.g = color.b =0.0;
    }
    
  }
}

void RenderObject::BindDiffuseTex(int m){
  if(diffTexOver){
    glBindTexture(GL_TEXTURE_2D, diffTex);
  } else {
    diffuseMap[m]->Bind();
  }
}

